import { expect } from '@jest/globals';
import * as compressing from 'compressing';
import * as execa from 'execa';
import * as fse from 'fs-extra';
import JSON5 from 'json5';
import StreamZip from 'node-stream-zip';
import path from 'path';

import { BuildDirConst } from '../../../src/const/build-directory-const';
import { InjectUtil } from '../../../src/utils/inject-util';
import { initExecuteCommand, runHvigorAsync } from '../common/common';

import { APIType, ModuleType, ModuleTypeMap, nodePath, SignType } from './project-builder/models/constants';
import { Project } from './project-builder/models/project-models';
import { TestCaseFun } from './task-builder/test-case';
import { commonConfig } from './common-config';

export const resourcesPath: string = path.join(__dirname, '../', '../', 'resources');

const TGZ_PRE_FIX = '1F8B';

/**
 * build app的执行函数
 *
 * @param {Project} project
 * @param {TestTaskOption} taskConfig
 * @param {ICaseRecord} record
 * @return {Promise<void>}
 */
export const buildApp: TestCaseFun = async (project, taskConfig, record) => {
  // 工程主目录
  const projectRoot = project.projectRootPath();
  const commands = initExecuteCommand({
    mode: 'project',
    product: 'default',
    tasks: 'assembleApp'
  });

  // 首次编译
  const result = await runHvigorAsync(projectRoot, commands);
  record.addStep('App首次编译', result);

  if (taskConfig.sign === SignType.NoSign) {
    expect(result.stderr)
      .toContain('Will skip sign \'app\'. No signingConfigs profile is configured in current project.');
  }
  expect(result.stdout).toContain('Finished ::SignApp');
  expect(result.stdout).toContain('BUILD SUCCESSFUL');

  // 二次编译
  const result2 = await runHvigorAsync(projectRoot, commands);
  record.addStep('App二次编译', result2);
  if (taskConfig.sign === SignType.NoSign) {
    expect(result.stderr)
      .toContain('Will skip sign \'app\'. No signingConfigs profile is configured in current project.');
  }
  expect(result2.stdout).toContain(`${taskConfig.sign === SignType.NoSign ? 'Finished' : 'UP-TO-DATE'} ::SignApp`);
  expect(result2.stdout).toContain('BUILD SUCCESSFUL');

  // 清除输出文件后再编译
  const customProjectBuildDir = path.resolve(InjectUtil.getBuildCacheParentDir(projectRoot, path.basename(projectRoot)),
    BuildDirConst.BUILD_ROOT);
  const output = path.resolve(customProjectBuildDir, 'outputs');
  await fse.remove(output);
  const result3 = await runHvigorAsync(projectRoot, commands);
  record.addStep('App清除输出文件后再编译', result3);

  if (taskConfig.sign === SignType.NoSign) {
    expect(result.stderr)
      .toContain('Will skip sign \'app\'. No signingConfigs profile is configured in current project.');
  }
  expect(result3.stdout).toContain('Finished ::SignApp');
  expect(result3.stdout).toContain('BUILD SUCCESSFUL');
};

/**
 * build app的执行函数
 *
 * @param {Project} project
 * @param {TestTaskOption} taskConfig
 * @param {ICaseRecord} record
 * @return {Promise<void>}
 */
export const buildAppAndCheckFile: TestCaseFun = async (project, taskConfig, record) => {
  await buildAppWithConfigAndCheck()(project, taskConfig, record);
};

/**
 * 使用指定配置构建工程
 *
 * @param {string} product
 * @return res
 */
// eslint-disable-next-line max-len
export const buildAppWithConfigAndCheck: (product?: string) => TestCaseFun = (product?: string) => async (project, taskConfig, record) => {
  // 工程主目录
  product = product ?? 'default';
  const projectRoot = project.projectRootPath();
  const commands = initExecuteCommand({
    mode: 'project',
    product: product,
    tasks: 'assembleApp'
  });

  commands.push('--parallel');
  commands.push('--incremental');

  // 编译
  const result = await runHvigorAsync(projectRoot, commands);
  record.addStep('App编译', result);

  if (taskConfig.sign === SignType.NoSign) {
    expect(result.stderr)
      .toContain('Will skip sign \'app\'. No signingConfigs profile is configured in current project.');
  }
  expect(result.stdout).toContain('Finished ::SignApp');
  expect(result.stdout).toContain('BUILD SUCCESSFUL');

  // 判断输出文件
  if (taskConfig.sign === SignType.Sign) {
    const outputDir = path.resolve(projectRoot, 'build', 'outputs', product, `myapplication-${product}-signed.app`);
    const existsOutPut = await fse.pathExists(outputDir);
    expect(existsOutPut).toBe(true);
  }
  const outputDir = path.resolve(projectRoot, 'build', 'outputs', product, `myapplication-${product}-unsigned.app`);
  const existsOutPut = await fse.pathExists(outputDir);
  expect(existsOutPut).toBe(true);
};

/**
 * 构建一个hap的执行函数
 *
 * @param {string} module
 * @return TestCaseFun
 */
// eslint-disable-next-line max-len
export const buildOneHap: (module?: string) => TestCaseFun = (module?: string) => async (project, taskConfig, record) => {
  // 工程主目录
  const projectRoot = project.projectRootPath();

  // 拼接命令行
  const commands = initExecuteCommand({
    mode: 'module',
    product: 'default',
    tasks: 'assembleHap'
    , ...(module ? { module: `${module}@default` } : {})
  });

  // 首次编译
  const result = await runHvigorAsync(projectRoot, commands);
  record.addStep(`${module} 模块首次编译`, result);
  const moduleStr = `${module ?? 'entry'}:default`;
  if (taskConfig.sign === SignType.NoSign) {
    expect(result.stderr)
      .toContain('Will skip sign \'hos_hap\'. No signingConfigs profile is configured in current project');
  }
  expect(result.stdout).toContain(`Finished :${moduleStr}@SignHap`);
  expect(result.stdout).toContain('BUILD SUCCESSFUL');

  // 二次编译
  const result2 = await runHvigorAsync(projectRoot, commands);
  record.addStep(`${module} 模块二次编译`, result2);
  if (taskConfig.sign === SignType.NoSign) {
    expect(result2.stderr)
      .toContain('Will skip sign \'hos_hap\'. No signingConfigs profile is configured in current project');
  }
  expect(result2.stdout).toContain(
    `${taskConfig.sign !== SignType.Sign ? 'Finished' : 'UP-TO-DATE'} :${moduleStr}@SignHap`);
  expect(result2.stdout).toContain('BUILD SUCCESSFUL');

  // 清除输出文件后再编译
  const output = path.resolve(projectRoot, `${module ?? 'entry'}/build/default/outputs`);
  await fse.remove(output);
  const result3 = await runHvigorAsync(projectRoot, commands);
  record.addStep(`${module} 模块清除输出文件后再编译`, result3);
  if (taskConfig.sign === SignType.NoSign) {
    expect(result3.stderr)
      .toContain('Will skip sign \'hos_hap\'. No signingConfigs profile is configured in current project');
  }
  expect(result3.stdout).toContain(`Finished :${moduleStr}@SignHap`);
  expect(result3.stdout).toContain('BUILD SUCCESSFUL');
};

/**
 * 构建一个hap的执行函数
 *
 * @param {string} module
 * @return TestCaseFun
 */
export const buildOneHapAndCheckFile: (module?: string) => TestCaseFun =
  (module?: string) => async (project, taskConfig, record) => {
    // 工程主目录
    const projectRoot = project.projectRootPath();

    // 拼接命令行
    const commands = initExecuteCommand({
      mode: 'module',
      product: 'default',
      tasks: 'assembleHap'
      , ...(module ? { module: `${module}@default` } : {})
    });

    // 编译
    const result = await runHvigorAsync(projectRoot, commands);
    record.addStep(`${module} 模块编译`, result);
    const moduleStr = `${module ?? 'entry'}:default`;
    if (taskConfig.sign === SignType.NoSign) {
      expect(result.stderr)
        .toContain('Will skip sign \'hos_hap\'. No signingConfigs profile is configured in current project');
    }
    expect(result.stdout).toContain(`Finished :${moduleStr}@SignHap`);
    expect(result.stdout).toContain('BUILD SUCCESSFUL');

    // 判断输出文件
    const modulePath = project.getModule(module ?? 'entry').moduleRootPath();
    const outPutPath = path.resolve(modulePath, 'build', 'default', 'outputs', 'default');
    const existsOutPutPath = await fse.pathExists(outPutPath);
    expect(existsOutPutPath).toBe(true);
    if (taskConfig.sign === SignType.Sign) {
      // eslint-disable-next-line max-len
      const existsSignHap = (await fse.readdir(outPutPath)).some(item => item.includes('-signed.hap'));
      expect(existsSignHap).toBe(true);
    }
    // eslint-disable-next-line max-len
    const existsSignHap = (await fse.readdir(outPutPath)).some(item => item.includes('-unsigned.hap'));
    expect(existsSignHap).toBe(true);
  };

/**
 * 构建一个hap的执行 make 函数
 *
 * @param {string} module
 * @param product product
 * @param target target
 * @return TestCaseFun
 */
export const makeOneHap: (module: string, product?: string, target?: string) => TestCaseFun =
  (module: string, product?: string, target?: string) => async (project, taskConfig, record) => {
    product = product ?? 'default';
    target = target ?? 'default';
    const commands = initExecuteCommand({
      mode: 'module',
      product: product,
      tasks: 'assembleHap',
      module: `${module}@${target}`
    });
    const result = await runHvigorAsync(project.projectRootPath(), commands);
    record.addStep(`选择${product}-${module}-${target}进行make构建`, result);
    expect(result.stdout).toContain('BUILD SUCCESSFUL');

    // 判断输出文件有且仅有一个hap包
    const outputs = path.resolve(project.getModule(module).moduleRootPath(), 'build', product, 'outputs');
    const outputsExists = await fse.pathExists(outputs);
    expect(outputsExists).toBe(true);
    const outputsDirs: string[] = await fse.readdir(outputs);
    expect(outputsDirs.length).toEqual(1);
    // eslint-disable-next-line max-len
    const hapExists = (await fse.readdir(path.resolve(outputs, target))).some(item => item.includes('.hap') && item.includes(module));
    expect(hapExists).toBe(true);
  };

/**
 * 对一个hap进行增量构建
 *
 * @param {string} module
 * @param product product
 * @param target target
 * @return TestCaseFun
 */
export const updateOneHap: (module: string, product?: string, target?: string) => TestCaseFun =
  (module: string, product?: string, target?: string) => async (project, taskConfig, record) => {
    product = product ?? 'default';
    target = target ?? 'default';
    const moduleStr = `${module}@${target}`;
    const commands = initExecuteCommand({
      mode: 'module',
      product: product,
      tasks: 'assembleHap',
      module: moduleStr
    });
    const result = await runHvigorAsync(project.projectRootPath(), commands);
    record.addStep(`选择${product}-${module}-${target}进行增量构建`, result);
    if (taskConfig.sign === SignType.NoSign) {
      expect(result.stderr)
        .toContain('Will skip sign \'hos_hap\'. No signingConfigs profile is configured in current project');
    }
    expect(result.stdout).toContain(`UP-TO-DATE :${module}:${target}`);
    expect(result.stdout).toContain('BUILD SUCCESSFUL');
  };

/**
 * 断言一个错误的buildApp场景
 *
 * @param {string} product
 * @return  TestCaseFun
 */
export const expectBuildAppError: (product?: string) => TestCaseFun =
  (product?: string) => async (project, taskConfig, record) => {
    product = product ?? 'default';
    const commands = initExecuteCommand({
      mode: 'project',
      product: product,
      tasks: 'assembleApp'
    });
    try {
      await runHvigorAsync(project.projectRootPath(), commands);

      // 正常路径走不到这里，预判一个异常
      expect('测试场景执行异常抵达此处').toEqual('测试场景执行正常不抵达此处');
    } catch (e: any) {
      if (e.constructor.name === 'JestAssertionError') {
        throw e;
      }
      record.addStep(`选择product-${product}进行build-app构建`, {
        stdout: e.stdout,
        stderr: e.stderr
      });
      expect(e.stderr).toContain('ERROR: Failed ::PackageApp');
    }
  };

/**
 * 构建一个hsp的执行函数
 *
 * @param {string} module
 * @return TestCaseFun
 */
// eslint-disable-next-line max-len
export const buildOneHsp: (module?: string) => TestCaseFun = (module?: string) => async (project, taskConfig, record) => {
  // 工程主目录
  const projectRoot = project.projectRootPath();

  // 拼接命令行
  const commands = initExecuteCommand({
    mode: 'module',
    product: 'default',
    tasks: 'assembleHsp'
    , ...(module ? { module: `${module}@default` } : {})
  });

  // 首次编译
  const result = await runHvigorAsync(projectRoot, commands);
  record.addStep(`${module} 模块首次编译`, result);
  const moduleStr = `${module ?? 'entry'}:default`;
  if (taskConfig.sign === SignType.NoSign) {
    expect(result.stderr)
      .toContain('Will skip sign \'hos_hap\'. No signingConfigs profile is configured in current project');
  }
  expect(result.stdout).toContain(`Finished :${moduleStr}@SignHsp`);
  expect(result.stdout).toContain('BUILD SUCCESSFUL');

  // 二次编译
  const result2 = await runHvigorAsync(projectRoot, commands);
  record.addStep(`${module} 模块二次编译`, result2);
  if (taskConfig.sign === SignType.NoSign) {
    expect(result2.stderr)
      .toContain('Will skip sign \'hos_hap\'. No signingConfigs profile is configured in current project');
  }
  expect(result2.stdout).toContain(`UP-TO-DATE :${moduleStr}@SignHsp`);
  expect(result2.stdout).toContain('BUILD SUCCESSFUL');

  // 清除输出文件后再编译
  const output = path.resolve(projectRoot, `${module ?? 'entry'}/build/default/outputs`);
  await fse.remove(output);
  const result3 = await runHvigorAsync(projectRoot, commands);
  record.addStep(`${module} 模块清除输出文件后再编译`, result3);
  if (taskConfig.sign === SignType.NoSign) {
    expect(result3.stderr)
      .toContain('Will skip sign \'hos_hap\'. No signingConfigs profile is configured in current project');
  }
  expect(result3.stdout).toContain(`Finished :${moduleStr}@SignHsp`);
  expect(result3.stdout).toContain('BUILD SUCCESSFUL');
};

/**
 * 构建一个hsp的执行函数
 *
 * @param {string} module
 * @return TestCaseFun
 */
// eslint-disable-next-line max-len
export const buildOneHspAndCheckFile: (module?: string) => TestCaseFun = (module?: string) => async (project, taskConfig, record) => {
  // 工程主目录
  const projectRoot = project.projectRootPath();

  // 拼接命令行
  const commands = initExecuteCommand({
    mode: 'module',
    product: 'default',
    tasks: 'assembleHsp'
    , ...(module ? { module: `${module}@default` } : {})
  });

  // 编译
  const result = await runHvigorAsync(projectRoot, commands);
  record.addStep(`${module} 模块编译`, result);
  const moduleStr = `${module ?? 'entry'}:default`;
  if (taskConfig.sign === SignType.NoSign) {
    expect(result.stderr)
      .toContain('Will skip sign \'hos_hap\'. No signingConfigs profile is configured in current project');
  }
  expect(result.stdout).toContain(`Finished :${moduleStr}@SignHsp`);
  expect(result.stdout).toContain('BUILD SUCCESSFUL');

  // 判断输出文件
  const modulePath = project.getModule(module ?? 'entry').moduleRootPath();
  // eslint-disable-next-line max-len
  const signHspPath = path.resolve(modulePath, 'build', 'default', 'outputs', 'default', `${module}.har`);
  const existsSignHspPath = await fse.pathExists(signHspPath);
  expect(existsSignHspPath).toBe(true);
  if (taskConfig.sign === SignType.Sign) {
    // eslint-disable-next-line max-len
    const signHspPath = path.resolve(modulePath, 'build', 'default', 'outputs', 'default', `${module}-default-signed.hsp`);
    const existsSignHspPath = await fse.pathExists(signHspPath);
    expect(existsSignHspPath).toBe(true);
  }
  // eslint-disable-next-line max-len
  const noSignHspPath = path.resolve(modulePath, 'build', 'default', 'outputs', 'default', `${module}-default-unsigned.hsp`);
  const existsNoSignHspPath = await fse.pathExists(noSignHspPath);
  expect(existsNoSignHspPath).toBe(true);
};

/**
 * 构建一个har的执行函数
 *
 * @param {string} module
 * @return TestCaseFun
 */
// eslint-disable-next-line max-len
export const buildOneHar: (module?: string) => TestCaseFun = (module?: string) => async (project, taskConfig, record) => {
  // 工程主目录
  const projectRoot = project.projectRootPath();

  // 拼接命令行
  const commands = initExecuteCommand({
    mode: 'module',
    product: 'default',
    tasks: 'assembleHar'
    , ...(module ? { module: `${module}@default` } : {})
  });

  // 首次编译
  const result = await runHvigorAsync(projectRoot, commands);
  record.addStep(`${module} 模块首次编译`, result);
  const moduleStr = `${module ?? 'entry'}:default`;
  if (project.option.apiType === APIType.STAGE) {
    expect(result.stdout).toContain(`Finished :${moduleStr}@PackageHar`);
  } else {
    expect(result.stdout).toContain(`Finished :${moduleStr}@LegacyPackageHar`);
  }
  expect(result.stdout).toContain('BUILD SUCCESSFUL');

  // 二次编译
  const result2 = await runHvigorAsync(projectRoot, commands);
  record.addStep(`${module} 模块二次编译`, result2);
  if (project.option.apiType === APIType.STAGE) {
    expect(result2.stdout).toContain(`UP-TO-DATE :${moduleStr}@PackageHar`);
  } else {
    expect(result2.stdout).toContain(`UP-TO-DATE :${moduleStr}@LegacyPackageHar`);
  }
  expect(result2.stdout).toContain('BUILD SUCCESSFUL');

  // 清除输出文件后再编译
  const output = path.resolve(projectRoot, `${module ?? 'entry'}/build/default/outputs`);
  await fse.remove(output);
  const result3 = await runHvigorAsync(projectRoot, commands);
  record.addStep(`${module} 模块清除输出文件后再编译`, result3);
  if (project.option.apiType === APIType.STAGE) {
    expect(result3.stdout).toContain(`Finished :${moduleStr}@PackageHar`);
  } else {
    expect(result3.stdout).toContain(`Finished :${moduleStr}@LegacyPackageHar`);
  }
  expect(result3.stdout).toContain('BUILD SUCCESSFUL');
};

/**
 * 构建一个har的执行函数
 *
 * @param {string} module
 * @return TestCaseFun
 */
// eslint-disable-next-line max-len
export const buildOneHarAndCheckFile: (module?: string) => TestCaseFun = (module?: string) => async (project, taskConfig, record) => {
  // 工程主目录
  const projectRoot = project.projectRootPath();

  // 拼接命令行
  const commands = initExecuteCommand({
    mode: 'module',
    product: 'default',
    tasks: 'assembleHar'
    , ...(module ? { module: `${module}@default` } : {})
  });

  // 首次编译
  const result = await runHvigorAsync(projectRoot, commands);
  record.addStep(`${module} 模块编译`, result);
  const moduleStr = `${module ?? 'entry'}:default`;
  if (project.option.apiType === APIType.STAGE) {
    expect(result.stdout).toContain(`Finished :${moduleStr}@PackageHar`);
  } else {
    expect(result.stdout).toContain(`Finished :${moduleStr}@LegacyPackageHar`);
  }
  expect(result.stdout).toContain('BUILD SUCCESSFUL');

  // 判断输出文件
  const modulePath = project.getModule(module ?? 'entry').moduleRootPath();
  // eslint-disable-next-line max-len
  const signHarPath = path.resolve(modulePath, 'build', 'default', 'outputs', 'default', `${module}.har`);
  const existsSignHarPath = await fse.pathExists(signHarPath);
  expect(existsSignHarPath).toBe(true);
};

/**
 * 构建全部hap执行函数
 *
 * @param {Project} project
 * @param {TestTaskOption} taskConfig
 * @param {ICaseRecord} record
 * @return {Promise<void>}
 */
export const buildHaps: TestCaseFun = async (project, taskConfig, record) => {
  // 遍历Build模块
  for (const moduleName of project.getModuleNames()) {
    const module = project.getModule(moduleName);
    if (ModuleTypeMap[module.option.moduleTemplateType] === ModuleType.Hap) {
      await buildOneHap(moduleName)(project, taskConfig, record);
    } else if (ModuleTypeMap[module.option.moduleTemplateType] === ModuleType.Har) {
      await buildOneHar(moduleName)(project, taskConfig, record);
    } else {
      await buildOneHsp(moduleName)(project, taskConfig, record);
    }
  }
};

/**
 * 构建全部hap执行函数
 *
 * @param {Project} project
 * @param {TestTaskOption} taskConfig
 * @param {ICaseRecord} record
 * @return {Promise<void>}
 */
export const buildHapsAndCheckFile: TestCaseFun = async (project, taskConfig, record) => {
  // 遍历Build模块
  for (const moduleName of project.getModuleNames()) {
    const module = project.getModule(moduleName);
    if (ModuleTypeMap[module.option.moduleTemplateType] === ModuleType.Hap) {
      await buildOneHapAndCheckFile(moduleName)(project, taskConfig, record);
    } else if (ModuleTypeMap[module.option.moduleTemplateType] === ModuleType.Har) {
      await buildOneHarAndCheckFile(moduleName)(project, taskConfig, record);
    } else {
      await buildOneHspAndCheckFile(moduleName)(project, taskConfig, record);
    }
  }
};

/**
 * 构建全部hap和app的执行函数
 *
 * @param {Project} project
 * @param {TestTaskOption} taskConfig
 * @param {ICaseRecord} record
 * @return {Promise<void>}
 */
export const buildAppAndHaps: TestCaseFun = async (project, taskConfig, record) => {
  // 1. 遍历Build模块
  await buildHaps(project, taskConfig, record);

  // 2. build app
  await buildApp(project, taskConfig, record);
};

/**
 * 构建全部hap和app的执行函数
 *
 * @param {Project} project
 * @param {TestTaskOption} taskConfig
 * @param {ICaseRecord} record
 * @return {Promise<void>}
 */
export const buildAppAndHapsAndCheckFile: TestCaseFun = async (project, taskConfig, record) => {
  // 1. 遍历Build模块
  await buildHapsAndCheckFile(project, taskConfig, record);

  // 2. build app
  await buildAppAndCheckFile(project, taskConfig, record);
};


/**
 * 清理构建产物
 *
 * @param {Project} project
 * @return {Promise<void>}
 */
export async function cleanTarget(project: Project): Promise<void> {
  const commandClean = initExecuteCommand({
    mode: 'module',
    product: 'default',
    tasks: 'clean'
  });
  await runHvigorAsync(project.projectRootPath(), commandClean);
}

/**
 * 获取zip文件的子文件列表
 *
 * @param {string} zipPath
 * @return {Promise<string[]>}
 */
export async function listZipEntries(zipPath: string): Promise<[string, number][]> {
  // 创建一个空数组，用于存放子文件名称
  const names: [string, number][] = [];

  let zip;
  try {
    // 创建一个StreamZip对象，传入一个包含文件路径的选项对象
    zip = new StreamZip.async({ file: zipPath });
    const entries = await zip.entries();
    for (const entry of Object.values(entries)) {
      names.push([entry.name, entry.size]);
    }
  } catch (err) {
    // 捕获并打印错误
    return [];
  } finally {
    if (zip) {
      try {
        await zip.close();
      } catch (e) {
        console.error(e);
      }
    }
  }

  // 返回子文件名称列表
  return names;
}

export async function decompressHapAndReadEntries(hapPath: string): Promise<[string, number][]> {
  // 判断文件是否存在，如果不存在
  if (!await fse.pathExists(hapPath)) {
    console.error(`hap file not exists: ${hapPath}`);
    return [];
  }

  // 获取文件所在的目录
  const dir = path.dirname(hapPath);
  const target = path.resolve(dir, 'temp');
  const result: [string, number][] = [];
  try {
    await fse.ensureDir(target);
    await compressing.zip.uncompress(hapPath, target);
    await readDirEntries(target, '', result);
  } finally {
    await fse.remove(target);
  }
  return result;
}

export async function decompressHarAndReadEntries(harPath: string): Promise<[string, number][]> {
  // 判断文件是否存在，如果不存在
  if (!await fse.pathExists(harPath)) {
    console.error(`har file not exists: ${harPath}`);
    return [];
  }

  // 读取子文件
  const result: [string, number][] = [];
  await decompressHarAndDoSomeThing(harPath, async (target) => {
    await readDirEntries(target, '', result);
  });
  return result;
}

export async function decompressHarAndDoSomeThing(harPath: string, operation: (taget: string) => Promise<void>) {
  // 判断文件是否存在，如果不存在
  if (!await fse.pathExists(harPath)) {
    console.error(`har file not exists: ${harPath}`);
    return;
  }

  // 获取文件所在的目录
  const dir = path.dirname(harPath);
  const target = path.resolve(dir, 'temp');
  const result: [string, number][] = [];
  try {
    await fse.ensureDir(target);
    if (await getFileType(harPath) === 'tgz') {
      await compressing.tgz.uncompress(harPath, target);
    } else {
      await compressing.tar.uncompress(harPath, target);
    }
    await operation(target);
  } finally {
    await fse.remove(target);
  }
  return result;
}

/**
 * 获取文件的头文件类型
 *
 * @param {string} harFilePath
 * @return {Promise<string>}
 */
async function getFileType(harFilePath: string): Promise<string> {
  // 读取文件的二进制数据，返回一个Buffer对象
  const buffer = await fse.readFile(harFilePath);

  // 将Buffer对象转换为16进制字符串
  const hexString = buffer.toString('hex').substring(0, 4).toUpperCase();

  // 判断16进制字符串是否以tgz或tar的前缀码开头
  if (hexString.startsWith(TGZ_PRE_FIX)) {
    return 'tgz';
  } else {
    return 'tar';
  }
}

async function readDirEntries(dirPath: string, parent: string, arr: [string, number][]) {
  const files = await fse.readdir(dirPath);
  for (const file of files) {
    const filePath = path.join(dirPath, file);
    const stat = await fse.stat(filePath);
    const entryName = parent !== '' ? `${parent}/${file}` : file;
    arr.push([entryName, stat.size]);
    if (stat.isDirectory()) {
      await readDirEntries(filePath, entryName, arr);
    }
  }
}

/**
 * 不解压读取子文件内容
 *
 * @param {string} zipPath
 * @param filePath
 * @return content
 */
export async function readZipEntry(zipPath: string, filePath: string): Promise<string | undefined> {
  let zip;
  try {
    zip = new StreamZip.async({ file: zipPath });
    const entries = await zip.entries();
    for (const entry of Object.values(entries)) {
      if (entry.name === filePath) {
        const data = await zip.entryData(filePath);
        return data.toString();
      }
    }

    // 关闭压缩文件
    await zip.close();
  } catch (err) {
    return undefined;
  } finally {
    if (zip) {
      await zip.close();
    }
  }
  return undefined;
}

async function updateStringConfig(moduleResourcesPath: string) {
  // 写入EntryFormAbility_label和EntryFormAbility_desc
  const stringList = ['base', 'en_US', 'zh_CN'];
  for (const string of stringList) {
    const stringJsonPath = path.resolve(moduleResourcesPath, string, 'element', 'string.json');
    const stringData = await fse.readFile(stringJsonPath);
    const stringJson = JSON.parse(stringData.toString());
    stringJson['string'].push({
      'name': 'EntryFormAbility_desc',
      'value': 'form_description'
    });
    stringJson['string'].push({
      'name': 'EntryFormAbility_label',
      'value': 'form_label'
    });
    stringJson['string'].push({
      'name': 'card_title',
      'value': 'Title'
    });
    stringJson['string'].push({
      'name': 'introduction',
      'value': 'Introduction'
    });
    stringJson['string'].push({
      'name': 'title_immersive',
      'value': 'Today\'s delicious food'
    });
    stringJson['string'].push({
      'name': 'detail_immersive',
      'value': 'A bowl of fragrant fried noodles, back to childhood memories'
    });
    await fse.writeFile(stringJsonPath, JSON.stringify(stringJson, null, 2), 'utf-8');
  }
}

async function updateColorConfig(moduleResourcesPath: string) {
  const colorJsonPath = path.resolve(moduleResourcesPath, 'base', 'element', 'color.json');
  const colorData = await fse.readFile(colorJsonPath);
  const colorJson = JSON.parse(colorData.toString());
  colorJson['color'] = [
    {
      'name': 'start_window_background',
      'value': '#FFFFFF'
    },
    {
      'name': 'item_title_font',
      'value': '#E6000000'
    },
    {
      'name': 'information_title_font',
      'value': '#E6000000'
    },
    {
      'name': 'title_font',
      'value': '#E5FFFFFF'
    },
    {
      'name': 'item_text_font',
      'value': '#99000000'
    },
    {
      'name': 'image_info_background',
      'value': '#FFFFFF'
    },
    {
      'name': 'immersive_item_text',
      'value': '#99FFFFFF'
    }
  ];
  await fse.writeFile(colorJsonPath, JSON.stringify(colorJson, null, 2), 'utf-8');
}

/**
 * 给模块添加服务卡片
 *
 * @param {string} modulePath
 * @param {'HelloWord' | 'Image' | 'Immersive'} cards
 * @return {Promise<void>}
 */
// eslint-disable-next-line max-len
export async function buildServiceCard(modulePath: string, ...cards: ('HelloWorld' | 'Image' | 'Immersive')[]): Promise<void> {
  const moduleResourcesPath = path.resolve(modulePath, 'src', 'main', 'resources');

  // 1. 获取模块ets路径
  const etsPath = path.resolve(modulePath, 'src', 'main', 'ets');

  // 2. 复制资源文件
  await fse.ensureDir(path.resolve(etsPath, 'entryformability'));
  await fse.copyFile(path.resolve(resourcesPath, 'extra', 'service-card', 'EntryFormAbility.ets'),
    path.resolve(etsPath, 'entryformability', 'EntryFormAbility.ets'));
  await fse.copyFile(path.resolve(resourcesPath, 'extra', 'service-card', 'ic_widget.png'),
    path.resolve(moduleResourcesPath, 'base', 'media', 'ic_widget.png'));
  await fse.copyFile(path.resolve(resourcesPath, 'extra', 'service-card', 'float.json'),
    path.resolve(moduleResourcesPath, 'base', 'element', 'float.json'));

  // 3. 遍历卡片类型生成卡片
  const forms = [];
  for (let i = 0; i < cards.length; i++) {
    const card = cards[i];
    const sourcePath = path.resolve(resourcesPath, 'extra', 'service-card', `${card}WidgetCard.ets`);
    const destPath = path.resolve(etsPath, `widget${i === 0 ? '' : i}`, 'pages', `Widget${i === 0 ? '' : i}Card.ets`);
    await fse.ensureDir(path.resolve(etsPath, `widget${i === 0 ? '' : i}`, 'pages'));
    await fse.copyFile(sourcePath, destPath);

    forms.push({
      'name': `widget${i === 0 ? '' : i}`,
      'description': 'This is a service widget.',
      'src': `./ets/widget${i === 0 ? '' : i}/pages/Widget${i === 0 ? '' : i}Card.ets`,
      'uiSyntax': 'arkts',
      'window': { 'designWidth': 720, 'autoDesignWidth': true },
      'colorMode': 'auto',
      'isDynamic': false,
      'isDefault': (i === 0),
      'updateEnabled': false,
      'scheduledUpdateTime': '10:30',
      'updateDuration': 1,
      'defaultDimension': '2*2',
      'supportDimensions': ['2*2']
    });
  }

  // 4. 修改module.json5
  const moduleJson5Path = path.resolve(modulePath, 'src', 'main', 'module.json5');
  const moduleJson5Data = await fse.readFile(moduleJson5Path);
  const moduleJson5 = JSON5.parse(moduleJson5Data.toString());
  moduleJson5['module']['extensionAbilities'] = [{
    'name': 'EntryFormAbility',
    'srcEntry': './ets/entryformability/EntryFormAbility.ets',
    'label': '$string:EntryFormAbility_label',
    'description': '$string:EntryFormAbility_desc',
    'type': 'form',
    'metadata': [{ 'name': 'ohos.extension.form', 'resource': '$profile:form_config' }]
  }];
  await fse.writeFile(moduleJson5Path, JSON5.stringify(moduleJson5, null, 2), 'utf-8');
  await updateStringConfig(moduleResourcesPath);
  await updateColorConfig(moduleResourcesPath);

  // 5. 增加form_config
  const formConfigPath = path.resolve(moduleResourcesPath, 'base', 'profile', 'form_config.json');
  const formJson = { 'forms': forms };

  await fse.writeFile(formConfigPath, JSON.stringify(formJson, null, 2), 'utf-8');
}

/**
 * 给模块添加oh-package dev依赖
 *
 * @param {string} modulePath
 * @param {{[p: string]: string}} dependencies
 * @return {Promise<void>}
 */
export async function addOhPackageDevDps(modulePath: string, dependencies: { [key: string]: string }): Promise<void> {
  const ohPackageJsonPath = path.resolve(modulePath, 'oh-package.json5');
  await commonJson5Operation(ohPackageJsonPath, async (json5: any) => {
    for (const [packageName, version] of Object.entries(dependencies)) {
      json5['devDependencies'][packageName] = version;
    }
  });
  await execa.command(commonConfig.ohpmInstallCommand, {
    cwd: modulePath
  });
}

/**
 * 给模块添加oh-package依赖
 *
 * @param {string} modulePath
 * @param {{[p: string]: string}} dependencies
 * @return {Promise<void>}
 */
export async function addOhPackageDps(modulePath: string, dependencies: { [key: string]: string }): Promise<void> {
  const ohPackageJsonPath = path.resolve(modulePath, 'oh-package.json5');
  await commonJson5Operation(ohPackageJsonPath, async (json5: any) => {
    for (const [packageName, version] of Object.entries(dependencies)) {
      json5['dependencies'][packageName] = version;
    }
  });
  await execa.command(commonConfig.ohpmInstallCommand, {
    cwd: modulePath
  });
}

/**
 * 通用json5文件操作
 *
 * @param {string} json5Path
 * @param {(json5: any) => Promise<void>} operation
 * @return  res
 */
export async function commonJson5Operation(json5Path: string, operation: (json5: any) => Promise<void>) {
  await commonFileOperation(json5Path, async (buffer: Buffer) => {
    const ohPackageJson5 = JSON5.parse(buffer.toString());
    await operation(ohPackageJson5);
    return JSON5.stringify(ohPackageJson5, null, 2);
  });
}

/**
 * 通用json文件操作
 *
 * @param {string} jsonPath
 * @param {(json: any) => Promise<void>} operation
 * @return  res
 */
export async function commonJsonOperation(jsonPath: string, operation: (json: any) => Promise<void>) {
  await commonFileOperation(jsonPath, async (buffer: Buffer) => {
    const ohPackageJson = JSON.parse(buffer.toString());
    await operation(ohPackageJson);
    return JSON.stringify(ohPackageJson, null, 2);
  });
}

/**
 * 通用读写文件操作
 *
 * @param {string} filePath
 * @param {(buffer: Buffer) => Promise<string>} operation
 * @return res
 */
export async function commonFileOperation(filePath: string, operation: (buffer: Buffer) => Promise<string>) {
  const fileData = await fse.readFile(filePath);
  const fileStr = await operation(fileData);
  await fse.writeFile(filePath, fileStr, 'utf-8');
}

/**
 * json 文件读取
 *
 * @param {string} filePath
 * @return {Promise<JSON>}
 */
export async function readJson(filePath: string): Promise<JSON> {
  const buffer = await fse.readFile(filePath);
  return JSON.parse(buffer.toString());
}

/**
 * json 文件读取
 *
 * @param {string} filePath
 * @return {Promise<JSON>}
 */
export async function readJson5(filePath: string): Promise<any> {
  const buffer = await fse.readFile(filePath);
  return JSON5.parse(buffer.toString());
}

/**
 * 给某个模块初始化预览配置文件
 *
 * @param {string} modulePath
 * @param moduleName
 * @param page
 * @return {Promise<void>}
 */
export async function initPreviewConfigFile(modulePath: string, moduleName = 'entry', page = 'pages/Index') {
  const buildConfigJson = {
    'deviceType': 'phone,tablet,2in1',
    'checkEntry': 'true',
    'localPropertiesPath': path.resolve(modulePath, '../', 'local.properties'),
    'Path': nodePath,
    'note': 'false',
    'aceProfilePath': path.resolve(modulePath, '.preview/default/intermediates/res/default/resources/base/profile'),
    'isPreview': 'true',
    'hapMode': 'false',
    'buildMode': 'debug',
    'img2bin': 'true',
    'projectProfilePath': path.resolve(modulePath, '../', 'build-profile.json5'),
    'watchMode': 'true',
    'appResource': path.resolve(modulePath, '.preview/default/intermediates/res/default/ResourceTable.txt'),
    'logLevel': '3',
    'stageRouterConfig': {
      'contents': [
        `{"module":{"pages":"$profile:main_pages","name":"${moduleName}"}}`,
        `{"src":["${page}"]}`
      ],
      'paths': [
        path.resolve(modulePath, '.preview/default/intermediates/res/default/module.json'),
        path.resolve(modulePath, '.preview/default/intermediates/res/default/resources/base/profile/main_pages.json')
      ]
    },
    'port': '29912',
    'aceBuildJson': path.resolve(modulePath, '.preview/default/intermediates/loader/default/loader.json'),
    'aceModuleRoot': path.resolve(modulePath, 'src/main/ets'),
    'aceSoPath': path.resolve(modulePath, '.preview/cache/nativeDependencies.txt'),
    'cachePath': path.resolve(modulePath, '.preview/cache/.default'),
    'aceModuleBuild': path.resolve(modulePath, '.preview/default/intermediates/assets/default/ets'),
    'aceSuperVisualPath': path.resolve(modulePath, 'entry/src/main/supervisual'),
    'aceModuleJsonPath': path.resolve(modulePath, '.preview/default/intermediates/res/default/module.json')
  };

  const buildConfigPath = path.resolve(modulePath, '.preview/config/buildConfig.json');
  await fse.ensureFile(buildConfigPath);
  await fse.writeFile(buildConfigPath, JSON.stringify(buildConfigJson, null, 2), 'utf-8');
}
